﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;
using System.Net;
using System.Threading;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Remoting.Channels;
using RemoteObject;

namespace AllInOne
{
    class CloudService
    {
        static Dictionary<long, DownloadJob> cloudFileDictionary = new Dictionary<long, DownloadJob>();

        public static string LocalIPAddress;
        private static FileStream srv_fs;
        private static String port;

        static void Main(string[] args)
        {

            DBConnect connect = new DBConnect();
            //connect.Insert();

            LocalIPAddress = Dns.GetHostByName(Dns.GetHostName()).AddressList[0].ToString();
            Console.WriteLine("Welcome {0}.", LocalIPAddress);
            Console.Write("Is this cloud server?[y|n] ");
            String answer = Console.ReadLine().ToString();
            String consolelog;

            String cloudPath = "C:\\c\\server\\";
            String localPath;

            Common.initFreeBlockBuffer();


            if (String.Compare(answer, "y", true) == 0)
            {
                Common.RunAsServer = true;
                Common.CloudServerURL = String.Format("tcp://{0}:10001/RemoteObject.RemoteObjectDownloader", LocalIPAddress);
                Common.IndexServerURL = String.Format("tcp://{0}:10001/RemoteObject.IndexServer", LocalIPAddress);

                port = "10001";
                localPath = cloudPath;

                if (Directory.Exists(cloudPath) == false)
                {
                    Directory.CreateDirectory(cloudPath);
                }

                Common.localSite.siteType = RemoteObject.SiteType.cloudServer;
                Directory.CreateDirectory(Common.FileDisInfoPath);
                consolelog = "========================   Server   ===========================";
            }
            else
            {
                Common.RunAsServer = false;
                Console.Write("Please define the Cloud Server IP (default 127.0.0.1) : ");
                String cloudServerIP = Console.ReadLine().ToString();
                cloudServerIP = cloudServerIP != "" ? cloudServerIP : "127.0.0.1";
                Common.CloudServerURL = String.Format("tcp://{0}:10001/RemoteObject.RemoteObjectDownloader", cloudServerIP);
                Common.IndexServerURL = String.Format("tcp://{0}:10001/RemoteObject.IndexServer", cloudServerIP);
                Console.Write("Please define the Listening Port: (default 10002) ");
                port = Console.ReadLine().ToString();
                port = port != "" ? port : "10002";
                localPath = "C:\\c\\" + port + "\\";
                if (Directory.Exists(localPath) == false)
                {
                    Directory.CreateDirectory(localPath);
                }
                Common.localSite.siteType = RemoteObject.SiteType.cloudClient;
                consolelog = "========================   Client   ===========================";
            }

            Common.LocalPath = localPath;
            Directory.CreateDirectory(Common.LocalPath + Common.LocalPathOfPendingJob);

            // **********LOCAL SERVER STARTUP********** //
            TcpChannel chanTcp = new TcpChannel(Convert.ToInt32(port));
            ChannelServices.RegisterChannel(chanTcp, false);

            //HttpChannel chanHttp = new HttpChannel(Convert.ToInt32(port));
            //ChannelServices.RegisterChannel(chanHttp, false);

            RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject.RemoteObjectDownloader), "RemoteObject.RemoteObjectDownloader", WellKnownObjectMode.Singleton);
            RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject.RemoteObjectUploader), "RemoteObject.RemoteObjectUploader", WellKnownObjectMode.Singleton);
            RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject.IndexServer), "RemoteObject.IndexServer", WellKnownObjectMode.Singleton);

            //track the MarshalByRefObject object's life cycle
            //System.Runtime.Remoting.Services.TrackingServices.RegisterTrackingHandler(new MyTracking());

            Common.localSite.siteID = LocalIPAddress + ":" + port;
            Common.localSite.siteIP = LocalIPAddress + ":" + port;
            Common.localSite.siteName = LocalIPAddress + ":" + port;

            Common.IndexServerPartner.ConnectToIndexServer();
            Common.IndexServerPartner.RunHeartBeatThread();

            RemoteObject.RemoteObjectDownloader.OnUploadRequest += new RemoteObject.RemoteObjectDownloader.MessageHandler(Request_OnUploadRequest);
            RemoteObject.RemoteObjectDownloader.OnDistributeRequest += new RemoteObject.RemoteObjectDownloader.MessageHandler(Request_OnDistributeRequest);
            RemoteObject.RemoteObjectDownloader.OnStopUploadRequest += new RemoteObject.RemoteObjectDownloader.MessageHandler2(Request_OnStopUploadRequest);
            RemoteObject.RemoteObjectDownloader.OnSyncRequest += new RemoteObject.RemoteObjectDownloader.MessageHandler3(Request_OnSyncRequest);
            RemoteObject.RemoteObjectDownloader.OnJobDistributionRequest += new RemoteObject.RemoteObjectDownloader.MessageHandler4(Request_OnJobListRequest);
            RemoteObject.RemoteObjectDownloader.OnDistributedJobRequest += new RemoteObject.RemoteObjectDownloader.MessageHandler5(Request_DistributedJob);
            // **********LOCAL SERVER STARTUP********** //

            srv_fs = new FileStream((localPath.Contains("10001") ? cloudPath : localPath) + "file.lst", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
            if (srv_fs.Length != 0)
            {
                List<DownloadJobTicket> list = (new BinaryFormatter()).Deserialize(srv_fs) as List<DownloadJobTicket>;
                foreach (DownloadJobTicket ticket in list)
                {
                    DownloadJob downloadJob = new DownloadJob(ticket);
                    cloudFileDictionary.Add(++RemoteObject.RemoteObjectDownloader.clientID, downloadJob);
                    //Thread thread = new Thread(new ThreadStart(downloadJob.StartDownload));
                    //thread.Start();
                    //Thread.Sleep(100);
                }
            }

            while (true)
            {
                try
                {
                    Console.WriteLine();
                    Console.WriteLine(consolelog);
                    Console.WriteLine("= [DOWN]:download;  [DOWNDIS]:download a distributed file;  ==");
                    Console.WriteLine("= [UP]:upload;  [DIS]:distribute;  [SHOW]:show file list;   ==");
                    Console.WriteLine("= [ADD]:add file index; [DEL]:delete file index             ==");
                    Console.WriteLine("= [INDEX]:show index; [SITE]:show alive sites; [QUIT]:quit  ==");
                    Console.WriteLine("==============================================================");
                    string input = Console.ReadLine();
                    if (String.Compare(input.Trim(), "ADD", true) == 0)
                    {
                        Console.Write("File name: ");
                        string name = Console.ReadLine();
                        Console.WriteLine("AddFile to IndexServer return : {0} ", Common.IndexServerPartner.AddFileToCloud(localPath + name));
                    }
                    else if (String.Compare(input.Trim(), "DEL", true) == 0)
                    {
                        Console.Write("File name: ");
                        string name = Console.ReadLine();

                        String fn = name;
                        if (!File.Exists(Common.LocalPath + fn))
                        {
                            Console.Write("File doesn't exists!");
                            continue;
                        }
                        Console.WriteLine("Delete file dis info from IndexServer return : {0} ", Common.IndexServerPartner.RemoveFileFromCloud(fn));
                        File.Delete(Common.LocalPath + fn);
                        File.Delete(Common.LocalPath + fn + Common.DownloadNetFileExtension);
                        File.Delete(Common.LocalPath + fn + Common.UploadNetFileExtension);
                    }
                    else if (String.Compare(input.Trim(), "RmDel", true) == 0)
                    {
                        Console.Write("File name: ");
                        string name = Console.ReadLine();

                        List<FileDisInfo> fdi_list = Common.IndexServerObject.GetFileDisInfoList(Common.indexServerChannelID, name);
                        if (fdi_list == null)
                        {
                            Console.WriteLine("Requested File is not available");
                            continue;
                        }

                        List<Site> avtiveSites = Common.IndexServerObject.GetAliveSites(Common.indexServerChannelID);
                        List<FileDisInfoItem> fileDisInfoItems = fdi_list[0].fdiiList;
                        Console.WriteLine("The Requested File Exists On Following Sites: ");
                        int j = 0;
                        foreach (FileDisInfoItem fdii in fileDisInfoItems)
                        {
                            Boolean siteReady = false;
                            foreach (Site site in avtiveSites)
                            {
                                if (site.siteID == fdii.locatedSite.siteIP)
                                {
                                    siteReady = true;
                                    break;
                                }
                            }
                            Console.WriteLine("  \t{0}\t{1}\t{2}\t{3}", ++j, fdii.locatedSite.siteIP, siteReady ? "Alive" : "Offline", (fdii.integrtiy.getCompleteRate() * 100.0).ToString("f1") + "%");
                        }

                        Console.WriteLine("Input the Site Code, Deviding using ','");
                        string siteID = Console.ReadLine();
                        string[] split = siteID.Split(new Char[] { ',' });

                        List<FileDisInfoItem> fromSites = new List<FileDisInfoItem>();
                        if (split == null || split.Length == 0)
                        {
                            fromSites = fileDisInfoItems;
                        }
                        else
                        {
                            foreach (string id in split)
                            {
                                if (Convert.ToInt32(id) >= 1 && Convert.ToInt32(id) <= fileDisInfoItems.Count)
                                    fromSites.Add(fileDisInfoItems[Convert.ToInt32(id) - 1]);
                            }
                        }

                        if (fromSites.Count > 0)
                        {
                            RemoteTaskRequest rmReq = new RemoteTaskRequest(Common.IndexServerObject.rmApplyUniqueTaskID());
                            foreach( FileDisInfoItem fdii in fromSites ){
                                rmReq.AddRemoteTask( new RTaskFileDelete( name, fdii.locatedSite ) );
                            }
                            Common.IndexServerObject.rmSubmitRemoteTaskToIndexServer(Common.indexServerChannelID, rmReq);
                            Console.WriteLine("PROCESSING: {0} {1}", input, name);
                        }
                    }
                    else if (String.Compare(input.Trim(), "DOWN", true) == 0 || String.Compare(input.Trim(), "DOWN-C", true) == 0)
                    {
                        Console.Write("File name: ");
                        string name = Console.ReadLine();

                        List<FileDisInfo> fdi_list = Common.IndexServerObject.GetFileDisInfoList(Common.indexServerChannelID, name );
                        if(fdi_list == null)
                        {
                            Console.WriteLine("Requested File is not available");
                            continue;
                        }

                        List<Site> avtiveSites = Common.IndexServerObject.GetAliveSites(Common.indexServerChannelID);
                        if( avtiveSites != null )
                        {
                            //remove local site from list;
                            for (int i = 0; i < avtiveSites.Count; i++)
                            {
                                Site site = avtiveSites[i];
                                if (site.siteID == Common.localSite.siteID)
                                {
                                    avtiveSites.RemoveAt(i);
                                    break;
                                }
                            }
                        }

                        List<FileDisInfoItem> fileDisInfoItems = fdi_list[0].fdiiList;
                        Console.WriteLine("The Requested File Exists On Following Sites: ");
                        int j = 0;
                        foreach (FileDisInfoItem fdii in fileDisInfoItems)
                        {
                            Boolean siteReady = false;
                            foreach (Site site in avtiveSites)
                            {
                                if (site.siteID == fdii.locatedSite.siteIP)
                                {
                                    siteReady = true;
                                    break;
                                }
                            }
                            Console.WriteLine("  \t{0}\t{1}\t{2}\t{3}", ++j, fdii.locatedSite.siteIP, siteReady ? "Alive" : "Offline", (fdii.integrtiy.getCompleteRate() * 100.0).ToString("f1") + "%");
                        }

                        Console.WriteLine("Input the Site Code, Deviding using ','");
                        string siteID = Console.ReadLine();
                        string[] split = siteID.Split(new Char[] { ',' });

                        List<FileDisInfoItem> fromSites = new List<FileDisInfoItem>();
                        if( split == null || split.Length == 0 )
                        {
                            fromSites = fileDisInfoItems;
                        }
                        else
                        {
                            foreach (string id in split)
                            {
                                if( Convert.ToInt32(id) >= 1 && Convert.ToInt32(id) <= fileDisInfoItems.Count )
                                    fromSites.Add(fileDisInfoItems[Convert.ToInt32(id) - 1]);
                            }
                        }

                        if (fromSites.Count > 0)
                        {
                            DownloadJob downloadJob = new DownloadJob(String.Concat(localPath, name), name, fromSites);
                            downloadJob.useCompressionMode = (String.Compare(input.Trim(), "DOWN-C", true) == 0);
                            Thread thread = new Thread(new ThreadStart(downloadJob.Start));
                            thread.Start();
                            Console.WriteLine("PROCESSING: {0} {1}", input, name);
                        }
                    }
                    else if (String.Compare(input.Trim(), "UPLOAD", true) == 0 || String.Compare(input.Trim(), "UPLOAD-C", true) == 0)
                    {
                        //get upload file name
                        Console.Write("File name: ");
                        string name = Console.ReadLine();

                        //get target site
                        List<Site> sites = Common.IndexServerObject.GetAliveSites(Common.indexServerChannelID);
                        if (sites != null)
                        {
                            //remove local site from list;
                            for(int i = 0; i < sites.Count; i++ )
                            {
                                Site site = sites[i];
                                if (site.siteID == Common.localSite.siteID)
                                {
                                    sites.RemoveAt(i);
                                    break;
                                }
                            }
                            int j = 0;
                            foreach (Site site in sites)
                            {
                                Console.WriteLine("{0}\t{1}", ++j, site.siteIP);
                            }
                        }

                        Console.WriteLine("Input the Target Site Code: ");
                        int siteIndex = Convert.ToInt32(Console.ReadLine());
                        if (siteIndex < 1 || siteIndex > sites.Count)
                        {
                            Console.WriteLine("Site code is invalid! Uploading cancelled.");
                            continue;
                        }

                        Site siteUploadTo = sites[siteIndex-1];
                        UploadJob uploadJob = new UploadJob(String.Concat(localPath, name), name, siteUploadTo);
                        if ( uploadJob.getLocalFileLength() <= 0 )
                        {
                            Console.WriteLine("Local file is invalid! Uploading cancelled.");
                            continue;
                        }
                        uploadJob.useCompressionMode = ( String.Compare(input.Trim(), "UPLOAD-C", true) == 0);
                        Thread thread = new Thread(new ThreadStart(uploadJob.Start));
                        thread.Start();

                        Console.WriteLine("PROCESSING: {0} {1}", input, name);
                    }
                    else if (String.Compare(input.Trim(), "DOWNDIS", true) == 0)
                    {
                        Console.Write("File name: ");
                        string name = Console.ReadLine();
                        Console.WriteLine("PROCESSING: {0} {1}...", input, name);
                        DistributedFileRequestor disFileReq = new DistributedFileRequestor(name);
                        Thread thread = new Thread(new ThreadStart(disFileReq.Run));
                        thread.Start();
                    }
                    else if (String.Compare(input.Trim(), "UP", true) == 0)
                    {
                        Console.Write("File name: ");
                        string name = Console.ReadLine();
                        Console.WriteLine("PROCESSING: {0} {1}...", input, name);
                        UploadRequestor upReq = new UploadRequestor(String.Concat(cloudPath, name), String.Concat(localPath, name), port);
                        Thread thread = new Thread(new ThreadStart(upReq.StartUpload));
                        thread.Start();
                    }
                    else if (String.Compare(input.Trim(), "DIS", true) == 0)
                    {
                        Console.Write("File name: ");
                        string name = Console.ReadLine();
                        Console.WriteLine("PROCESSING: {0} {1}...", input, name);
                        DistributeRequestor disReq = new DistributeRequestor(String.Concat(cloudPath, name), String.Concat(localPath, name), port);
                        Thread thread = new Thread(new ThreadStart(disReq.Run));
                        thread.Start();
                    }
                    else if (String.Compare(input.Trim(), "SHOW", true) == 0)
                    {
                        List<DownloadJobTicket> jobList = FileDistributionRequestor.getInstance().GetFileDistributionInfo();
                        if (jobList.Count == 0)
                        {
                            Console.WriteLine();
                            Console.WriteLine("******FILE LIST EMPTY******");
                            Console.WriteLine();
                        }
                        else
                        {
                            Console.WriteLine("******FILE LIST START******");
                            foreach (DownloadJobTicket job in jobList)
                            {
                                Console.WriteLine();
                                String name = job.fnLocal.Substring(job.fnLocal.LastIndexOf("\\") + 1);
                                float fileLength = job.fileLength / 1024.0f / 1024.0f;
                                Console.WriteLine("File Name:{0}, Size:{1} MB, Status:{2}", name, fileLength, job.jobStatus);
                            }
                            Console.WriteLine();
                            Console.WriteLine("*******FILE LIST END*******");
                        }
                    }
                    else if (String.Compare(input.Trim(), "INDEX", true) == 0)
                    {
                        List<FileDisInfo> fdi_list = Common.IndexServerObject.GetFileDisInfoList(Common.indexServerChannelID, "");
                        if (fdi_list == null || fdi_list.Count == 0)
                        {
                            Console.WriteLine();
                            Console.WriteLine("******FILE LIST EMPTY******");
                            Console.WriteLine();
                        }
                        else
                        {
                            int i = 0;
                            foreach (FileDisInfo fdi in fdi_list)
                            {
                                Console.WriteLine(" {0}\t{1}", ++i, fdi.fileName);
                                int j = 0;
                                foreach (FileDisInfoItem fdii in fdi.fdiiList)
                                {
                                    Console.WriteLine("  \t{0}\t{1}\t{2}\t{3}", ++j, fdii.locatedSite.siteIP, (1.0*fdii.integrtiy.totalFileSize/1024/1024).ToString("f2"), (fdii.integrtiy.getCompleteRate() * 100.0).ToString("f1") + "%");
                                }
                            }
                        }
                    }
                    else if (String.Compare(input.Trim(), "SITE", true) == 0)
                    {
                        List<Site> sites = Common.IndexServerObject.GetAliveSites(Common.indexServerChannelID);
                        if (sites != null)
                        {
                            int i = 0;
                            foreach (Site site in sites)
                            {
                                Console.WriteLine("{0}\t{1}", ++i, site.siteIP);
                            }
                        }
                    }
                    else if (String.Compare(input.Trim(), "QUIT", true) == 0)
                    {
                        break;
                    }
                    else
                    {
                        Console.WriteLine("NO SUCH COMMOND: \"{0}\". ", input);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }

            Common.ApplicationQuit = true;
            Common.IndexServerPartner.DisconnectToIndexServer();

            ///////////////////For Debug/////////////////////////////
            Console.WriteLine("Press any key to exit!");
            System.Console.ReadLine();
        }

        public static void Request_OnUploadRequest(string fnRemote, string fnLocal, string serviceURLTcp, long jobID) //
        {
            //init downloader on host
            DownloadJob downloadJob = new DownloadJob(fnLocal, fnRemote, serviceURLTcp);
            cloudFileDictionary.Add(jobID, downloadJob);
            Thread thread = new Thread(new ThreadStart(downloadJob.Start));
            thread.Start();
            Thread.Sleep(100);

            if (srv_fs != null)
            {
                srv_fs.SetLength(0);
                try
                {
                    (new BinaryFormatter()).Serialize(srv_fs, Request_OnJobListRequest());
                }
                catch (SerializationException e)
                {
                    Console.WriteLine("Failed to serialize. Reason: " + e.Message);
                    throw;
                }

            }
            
        }

        public static void Request_OnDistributeRequest(string fnRemote, string fnLocal, string serviceURLTcp, long jobID)
        {
            DownloadJob downloadJob = new DownloadJob(fnLocal, fnRemote, serviceURLTcp);
            cloudFileDictionary.Add(jobID, downloadJob);


            if (srv_fs != null)
            {
                srv_fs.SetLength(0);
                try
                {
                    (new BinaryFormatter()).Serialize(srv_fs, Request_OnJobListRequest());
                }
                catch (SerializationException e)
                {
                    Console.WriteLine("Failed to serialize. Reason: " + e.Message);
                    throw;
                }
            }
        }

        public static void Request_OnStopUploadRequest(long jobID)
        {
            DownloadJob job = ((DownloadJob)cloudFileDictionary[jobID]);
            job.Cancel();
        }

        public static RemoteObject.JobInfo Request_OnSyncRequest(long jobID)
        {
            DownloadJob job = ((DownloadJob)cloudFileDictionary[jobID]);
            RemoteObject.JobInfo jobInfo = new RemoteObject.JobInfo();
            jobInfo.progress = job.GetProgress();
            jobInfo.transferSpeed = job.GetTransferSpeed();
            return jobInfo;
        }

        public static List<DownloadJobTicket> Request_OnJobListRequest()
        {
            List<DownloadJob> jobList = new List<DownloadJob>(cloudFileDictionary.Values);
            List<DownloadJobTicket> ticketList = new List<DownloadJobTicket>();
            foreach (DownloadJob job in jobList)
            {
                ticketList.Add(job.GetDownloadJobTicket());
            }
            jobList.Clear();
            jobList = null;
            return ticketList ;
        }

        public static DownloadJob Request_DistributedJob(string jobName)
        {
            Dictionary<long, DownloadJob>.ValueCollection jobList = cloudFileDictionary.Values;
            foreach (DownloadJob djob in jobList)
            {
                if (String.Compare(djob.GetJobName(), jobName, true) == 0)
                {
                    djob.Start();
                    return djob;
                }
            }
            return null;
        }
    }
}
